package com.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FztxApplicationContext {
    private Class configClass;
    //每次创建好一个bean的定义信息，就存起来，一个文件路径下可能会有多个.class文件，key为bean的名称，value为bean的定义信息
    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
    //单例池，存放所有的单例bean
    private Map<String,Object> singleObjects = new HashMap<>();
    //就是一个list，存放bean的后置处理器
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    /**
     * Spring 启动，两大核心步骤
     * 1.扫描指定路径下的所有类（扫描的事target下的.class文件）
     * 2.创建实例bean（自动注入）--Spring启动的时候只会实例化非懒加载的单例bean
     *
     * @param configClass
     */
    public FztxApplicationContext(Class configClass) {
        this.configClass = configClass;

        /**
         *  扫描类，得到BeanDefinition（里面封装的bean的属性）
         *  依据@ComponentScan("com.fztx.service") 注解配置的路径扫描，路径可以为多个
         */
        scan(configClass);

        /**
         *  实例化非懒加载单例bean，分五步执行
         *  1.实例化
         *  2.属性填充
         *  3.初始化前
         *  4.Aware回调
         *  5.初始化
         *  6.初始化后
         *  7.添加到单例池
         */
        instanceSingletonBean();

    }

    /**
     * 实例化非懒加载单例bean
     */
    private void instanceSingletonBean() {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            //判断是否是非懒加载单例bean
            if (beanDefinition.getScopeValue().equals("singleton")) {
                //创建单例bean
                Object bean = doCreateBean(beanName, beanDefinition);
                //放到单例池中，getbean方法时单例bean可以直接到单例池中取
                singleObjects.put(beanName,bean);
            }
        }
    }

    /**
     * 创建bean
     */
    private Object doCreateBean(String beanName, BeanDefinition beanDefinition) {
        //基于bean的定义，也就是BeanDefinition创建bean
        Class clazz = beanDefinition.getBeanClass();
        Object instance = null;
        try {
            /**
             * 1.实例化bean
             * 这里取了无参构造方法，没有实现推断构造方法
             */
            instance = clazz.getConstructor().newInstance();

            /**
             *  2.属性填充
             *
             */
            //获取实例bean中的所有属性
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                //判断属性中是否有@Autowired 注解注入的
                if (field.isAnnotationPresent(Autowired.class)) {
                    //此处后面在补充先通过byType寻找，在通过byName寻找
                    //循环依赖的问题暂时不考虑
                    String fieldName = field.getName();
                    Object bean = getBean(fieldName); //直接通过bean的名字获得bean
                    field.setAccessible(true); //如果取得的field属性使用private的，则必须设置true才能访问，否则会报错
                    field.set(instance, bean);
                }
            }

            /**
             *  Bean后置处理器
             *  例如：UserService中有用@Autowired和@Resource注解注入的属性对象
             *       那么UserService bean实例化好之后，分别处理@Autowired和@Resource
             *       的内容
             *  Spring源码中处理@Autowired是AutowiredAnnotationBeanPostProcessor
             *                @Resource是CommonAnnotationBeanPostProcessor
             *
             */

            /**
             *  我们可以自己实现BeanPostProcessor接口去实现我们自己的功能
             *  bean后置处理器的两个方法可以进行扩展实现我们的需求
             */

            /**
             *  3.初始化前--->遍历bean的后置处理器list，执行postProcessBeforeInitialization初始化前操作
             *
             */
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
            }


            /**
             *  4.Aware回调--->判断当前创建的实例bean是否实现了BeanNameAware回调接口
             *  Spring在扫描带@Component注解的类时，会给类赋值一个名称（或者@Component中配置），
             *  此时需要知道bean对应的名称是什么，所以回调获取bean的名称
             */
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }

            /**
             *  5.初始化，校验spring创建的bean是否创建成功
             *  执行顺序放在实例bean、属性填充、Aware回调之后
             */
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }


            /**
             *  5.初始化前--->遍历bean的后置处理器list，执行postProcessAfterInitialization初始化后操作
             *  aop就是在这个地方实现
             */
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            }



        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return instance;
    }

    /**
     * Spring在启动的时候，扫描给定路径下的所有.class文件
     *
     * @param configClass
     */
    private void scan(Class configClass) {
        /**
         *  1.扫描指定路径下的所有类（扫描的是target下的.class文件）
         *  转化为BeanDefinition对象，最后添加到beanDefinitionMap中
         */
        //先得到扫描路径
        if (configClass.isAnnotationPresent(ComponentScan.class)) { //判断是否存在@ComponentScan注解
            //存在的话根据注解value的值去对应路径扫描class文件
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();
            System.out.println("Spring启动扫描的包路径地址：" + path);
            //扫描包路径得到路径下所有的.class文件
            List<Class> beanClasses = getBeanClasses(path);
            //遍历beanClasses，将bean的部分属性信息封装在BeanDefinition，
            //以便在之后获取的bean的时候，不要再次走一遍扫描 -->实例化的流程了
            for (Class clazz : beanClasses) {
                //判断当前bean有没有被@Component注解标识,只有加了@Component注解的类才加载到容器中
                if (clazz.isAnnotationPresent(Component.class)) {

                    //添加Bean的后置处理逻辑，Spring在扫描时，将实现BeanPostProcessor接口全部添加到后置处理器集合中
                    if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                        BeanPostProcessor instance = null;
                        try {
                            instance = (BeanPostProcessor) clazz.getConstructor().newInstance();
                        } catch (InstantiationException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        }
                        beanPostProcessorList.add(instance);
                    }else{
                        //一个实例bean，要么是被Spring自动生成，要么是从注解@Component上获取(注解不唯一，这里只举一个例子)
                        Component componentAnnotation = (Component) clazz.getAnnotation(Component.class);
                        //获取注解@Component中标识的bean的名称，例如@Component("userService")形式
                        String beanName = componentAnnotation.value();
                        //如果Component没有设置beanName，spring自动生成，首字母小写
                        if ("".equals(beanName)){
                            beanName = Introspector.decapitalize(clazz.getSimpleName());//spring底层自动生成beanname
                        }
                        //创建bean定义对象并设置属性
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setBeanClass(clazz);

                        //判断是否有@Scope注解
                        if (clazz.isAnnotationPresent(Scope.class)) {
                            Scope scopeAnnotation = (Scope) clazz.getAnnotation(Scope.class);
                            //获取单例或原型的值
                            String scopeValue = scopeAnnotation.value();
                            beanDefinition.setScopeValue(scopeValue);
                        }else{
                            //没有则默认是单例
                            beanDefinition.setScopeValue("singleton");
                        }

                        //判断是否有@Lazy懒加载注解
                        if (clazz.isAnnotationPresent(Lazy.class)) {
                            beanDefinition.setLazyValue(true);
                        }

                        beanDefinitionMap.put(beanName,beanDefinition);
                    }

                }
            }


        }
    }

    /**
     * 从指定的路径中获取bean
     * 此类为自己单独模拟获取的，写的比较简单，方便理解
     */
    private List<Class> getBeanClasses(String path) {
        List<Class> beanClasses = new ArrayList<>();
        //获取一个类加载器
        ClassLoader classLoader = FztxApplicationContext.class.getClassLoader();
        //根据类加载器获取指定路径的资源，传入相对路径，得到的是文件夹 file:/E:/TuLing/code/write-spring/target/classes/com/fztx/service
        URL resource = classLoader.getResource(path.replace(".", "/"));
        System.out.println("Spring扫描的路径地址：" + resource);
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            for (File f : file.listFiles()) {
                //得到class的文件路径+名字 E:\TuLing\code\write-spring\target\classes\com\fztx\service\UserService.class
                String absolutePath = f.getAbsolutePath();
                //由于此文件夹下可能存在其他非.class类型的文件，所以需要判断
                if (absolutePath.endsWith(".class")) {
                    //classloader加载类需要class的包+名
                    //获取.class文件的对应的类名
                    String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    //替换
                    className = className.replace("\\", ".");
                    //通过类加载器加载获取一个对象
                    try {
                        Class<?> clazz = classLoader.loadClass(className);
                        beanClasses.add(clazz);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
        return beanClasses;
    }

    /**
     * 获取Bean
     */
    public Object getBean(String beanName){

        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new NullPointerException();
        }

        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        //创建bean之前，判断一下是否为单例，
        //如果为单例，直接看单例池中是否有此实例bean，如果有直接取出，如果没有新创建一个单例bean，并且放入单例池中
        if (beanDefinition.getScopeValue().equals("singleton")) {
            Object bean = singleObjects.get(beanName);
            if(bean == null){
                bean = doCreateBean(beanName, beanDefinition);
                singleObjects.put(beanName,bean);
            }
            return bean;

        }else{
            //原型bean，根据beanDefinition去新创建一个bean
            Object bean = doCreateBean(beanName, beanDefinition);
            return bean;
        }

    }


}
